feat(share): flat end-of-conversation band + split copy-link button - #6369
Closed
tsahimatsliah wants to merge 9 commits into
Closed
feat(share): flat end-of-conversation band + split copy-link button#6369tsahimatsliah wants to merge 9 commits into
tsahimatsliah wants to merge 9 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Adds a `split` variant to ShareActions: two real buttons that read as one control. The left half copies the link and cross-fades its glyph to a green check; the right half is a chevron that opens the standard DropdownMenu with the social tiles. Geometry matches a standard Button at every size — height, radius, type scale and outer padding — deviating only at the shared edge, where both inner paddings tighten one step and the chevron drops its icon-only square. The divider is a single rule: the chevron half keeps its own DS border while the main half drops `border-r`, since two borders meeting is what reads as a double line. Borderless variants have no border to inherit and draw their own. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drops the card's fill and border for a single hairline rule separating the strip from the comments above it, and moves the trigger to the labelled split button. `variant="card"` keeps the heavier self-contained surface for surfaces that want it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every band state (either side of the threshold, flat vs card, in context below a comment list, a real 390px mobile frame) and the split control across all variants and sizes, with computed geometry printed beside the standard buttons so the guideline match is checkable rather than asserted. The auth/log/query decorator the share stories share is extracted to `share.mocks.tsx` — it had been copied verbatim into three story files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tsahimatsliah
force-pushed
the
claude/share-split-copy-button
branch
from
July 23, 2026 12:19
b0f60a3 to
598a1b0
Compare
Removes both gates: the `share_end_of_conversation` experiment flag (deleted, nothing else read it) and the `sharing_visibility` kill-switch check. The comment threshold is now the only condition, so the two-component split (gated wrapper + ungated band) collapses into one. `useSharingVisibility` and `sharing_visibility` stay — they gate the other surfaces in the initiative, this one just no longer consumes them. Tests for the discarded path are removed rather than inverted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review pass over the PR: - The copy confirmation was swapped in for every ShareActions variant, which silently restyled the icon-only triggers on the feed card, brief header and mobile share widget. Those keep the existing `secondary` fill; the green check is now the split control's alone. - The flat band set `py-4 pt-6`, which resolved correctly only because Tailwind emits `pt-*` after `py-*` in the stylesheet — class order in the attribute has no bearing on it. Now `pb-4 pt-6`. - `hasActiveDiscussion` lost its last external caller with the flag wrapper, so it is no longer exported. - Comment still cited the old "Share this discussion" label. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The chevron carried `pressed`, which renders `aria-pressed` and is painted as a held-down fill. Removed: the rotation is the only open-state signal the control wants, and Radix already sets `aria-expanded`, which is the correct semantic for a menu trigger. - The split dropdown repeated a "Share" heading under a trigger that already says so, and centred its tiles. Heading dropped and tiles aligned left. The popover used by the icon-only surfaces keeps both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `p-4` on DropdownMenuContent never applied — the dropdown's CSS module applies `p-1.5` at the same specificity and wins on stylesheet order, leaving 6px. And `flex-wrap` with `justify-start` collected all the leftover row width on one side, so the insets measured top 7 / right 41 / bottom 29 / left 15. Now `!p-4` so the override lands, and a 4-column grid whose columns divide the content box exactly. Measured insets: top 17 / right 17.8 / bottom 17 / left 17.8 — the 1.8px is cell-centring slack, identical on both sides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tsahimatsliah
added a commit
that referenced
this pull request
Jul 27, 2026
Share moves out of the buried "..." options menu into the header action row on both entity pages, as PR #6369's SplitShareButton: the label copies the link and the chevron drops the standard social list. - New `EntityShareAction` wraps `ShareActions` with the entity's link/text/cid and logs `ShareTag`/`ShareSource` with the provider and origin. `display` defaults to the split control; `icon` keeps the icon-only trigger. - Block leaves the row for the "..." menu, so the row is one Follow button plus identical secondary controls. Blocked is the exception: Unblock stays in the row, in the Follow slot that is empty then, and leaves the menu. - The notification bell takes ButtonVariant.Float so the bell, the copy control and the "..." button read as one treatment. - `CustomFeedOptionsMenu` gains `hideShare` so the in-menu Share entry is dropped where the visible control renders, never both. - `SourceActions` gains `showShare`; only /sources/[source] passes it, so the post page highlights widget keeps its existing row. No feature flag: this ships to everyone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tsahimatsliah
added a commit
that referenced
this pull request
Jul 27, 2026
Share moves out of the buried "..." options menu into the header action row on both entity pages, as PR #6369's SplitShareButton: the label copies the link and the chevron drops the standard social list. - New `EntityShareAction` wraps `ShareActions` with the entity's link/text/cid and logs `ShareTag`/`ShareSource` with the provider and origin. `display` defaults to the split control; `icon` keeps the icon-only trigger. - Block leaves the row for the "..." menu, so the row is one Follow button plus identical secondary controls. Blocked is the exception: Unblock stays in the row, in the Follow slot that is empty then, and leaves the menu. - The notification bell takes ButtonVariant.Float so the bell, the copy control and the "..." button read as one treatment. - `CustomFeedOptionsMenu` gains `hideShare` so the in-menu Share entry is dropped where the visible control renders, never both. - `SourceActions` gains `showShare`; only /sources/[source] passes it, so the post page highlights widget keeps its existing row. No feature flag: this ships to everyone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tsahimatsliah
added a commit
that referenced
this pull request
Jul 27, 2026
Share moves out of the buried "..." options menu into the header action row on both entity pages, as PR #6369's SplitShareButton: the label copies the link and the chevron drops the standard social list. - New `EntityShareAction` wraps `ShareActions` with the entity's link/text/cid and logs `ShareTag`/`ShareSource` with the provider and origin. `display` defaults to the split control; `icon` keeps the icon-only trigger. - Block leaves the row for the "..." menu, so the row is one Follow button plus identical secondary controls. Blocked is the exception: Unblock stays in the row, in the Follow slot that is empty then, and leaves the menu. - The notification bell takes ButtonVariant.Float so the bell, the copy control and the "..." button read as one treatment. - `CustomFeedOptionsMenu` gains `hideShare` so the in-menu Share entry is dropped where the visible control renders, never both. - `SourceActions` gains `showShare`; only /sources/[source] passes it, so the post page highlights widget keeps its existing row. No feature flag: this ships to everyone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The band's sharing tests covered copy (main half + dropdown tile) and native, but not the individual social networks. Adds a parametrised case for X, WhatsApp and LinkedIn asserting each logs LogEvent.SharePost with its own ShareProvider and Origin.EndOfConversation, opens a window, and never touches the clipboard — so a network share and a copy can't conflate in the funnel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
tomeredlich
added a commit
that referenced
this pull request
Sep 1, 2026
"Tsahi's button" is SplitShareButton from #6369 — copy link and a chevron holding the networks, meeting at one hairline — wrapped by ShareBand, which pairs it with a title and a line of copy. #6369 built it for exactly these two surfaces: the band below an active discussion and the prompt after an upvote. Neither PR landed. #6369 is closed and #6378 merged into it, so the components exist on no branch that ships. They are carried here — ShareBand, ShareActions, SplitShareButton, CopyStateIcon and the Origin they log against — and both prompts now render through ShareBand rather than the pair of hand-rolled rows I had built. If that stack revives, these two callers should collapse into its EndOfConversationShare and the copies here should go. The copy is theirs too, so the two bands read as one pair. Testing: shared 382 suites / 2680 tests, webapp 81 / 644, extension 6 / 52, package lint clean for shared and webapp. The ported ShareActions spec runs green here as it does there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich
added a commit
that referenced
this pull request
Sep 2, 2026
Three controls inside the brief itself: - The post page's highlight bar (#6556), scoped to a body container so a selection in the header or the preferences card never raises it. - A copy icon per bullet and paragraph, carrying the text and the brief link, after #6350's copy-summary icon. Each one is labelled with what it copies, since a brief carries a column of them. - A Snapshot on the Must know heading that captures its bullets as one card, with the claim before the colon as each line. None of these can be declared in JSX: the body is a single `<Markdown content={contentHtml} />`, so briefBodyBlocks reads the blocks back out of the rendered DOM — which is also the most faithful source, being what the reader is looking at — and the controls are portalled into them. Hosts are marked and swept before each run, because Markdown re-renders on its own for hover cards and the image modal. The closing band is #6369's ShareBand with the briefing's copy, so it cannot drift from the end-of-conversation band it was asked to match. All of it sits behind `briefing_share_controls`, default false. Mockup-to-eng-pass: 1 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich
added a commit
that referenced
this pull request
Sep 3, 2026
…o choose from The Snapshot button becomes a Share button: the icon and label are the ones people already recognise, and pressing it opens a menu that previews the designed 1080² card before offering it. The menu opens immediately with a skeleton rather than waiting on the render. Rasterizing takes a second or two, and a control that does nothing visible for that long reads as broken — so the capture is deferred a tick, letting the menu paint first, and Copy link stays usable while the card is still rendering. Seven layouts are up for review in Features/Snapshot/Menu styles, all live against the same card: rows, rows-centred, an overlay pill on the card, a compact no-preview variant, Tsahi's split button from #6369, and the two ways the image can sit in the share modal — its own section or a single tile. The `menuVariant` prop exists to serve that page and comes out with the losers. The image is one action, not a mode over the sheet's other targets. A link needs a destination, which is why there are twenty of them; an image does not, because the OS share sheet and the clipboard are the destinations, and no web share intent takes an image anyway. So the image owns its own action and the squads and external tiles keep sharing the link — which is what keeps this on one screen instead of two. Pressing it hands the PNG to the native sheet where the platform takes files, copies it to the clipboard otherwise (with a toast, since the clipboard has no UI of its own), and downloads it only as a last resort. Capture moves into `useSnapshotCapture` so the dropdown and the modal section render from one implementation. Its `isActive` gate matters twice: a feed never carries a 1080px card per item until someone asks to share, and because the modal pre-renders on open, the click handler reaches `clipboard.write` with no await in front of it — still inside the user gesture the clipboard requires. Two surfaces had no card at all, from the #6579 and #6581 reviews: - `FeedSnapshotCard` carries a shared feed's recipe — tags and source count — rather than its posts, which are a standing filter and would go stale. - `AwardSnapshotCard` names the sender. It is the only status moment that comes from someone else, and a gift with no giver reads as self-congratulation. Tsahi's `SplitShareButton` and `CopyStateIcon` come across from #6369 unchanged apart from four additive props — `onOpenChange`, `header`, `menuLayout` and a width override — so his grid is untouched when nobody passes them. `SocialShareList`, `SocialShareButton` and `SocialShareContainer` gain optional size and compact props, defaulting to today's values, so the dropdown can tighten without moving anything else in the app. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich
added a commit
that referenced
this pull request
Sep 3, 2026
Three controls inside the brief itself: - The post page's highlight bar (#6556), scoped to a body container so a selection in the header or the preferences card never raises it. - A copy icon per bullet and paragraph, carrying the text and the brief link, after #6350's copy-summary icon. Each one is labelled with what it copies, since a brief carries a column of them. - A Snapshot on the Must know heading that captures its bullets as one card, with the claim before the colon as each line. None of these can be declared in JSX: the body is a single `<Markdown content={contentHtml} />`, so briefBodyBlocks reads the blocks back out of the rendered DOM — which is also the most faithful source, being what the reader is looking at — and the controls are portalled into them. Hosts are marked and swept before each run, because Markdown re-renders on its own for hover cards and the image modal. The closing band is #6369's ShareBand with the briefing's copy, so it cannot drift from the end-of-conversation band it was asked to match. All of it sits behind `briefing_share_controls`, default false. Mockup-to-eng-pass: 1 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich
added a commit
that referenced
this pull request
Sep 3, 2026
Three controls inside the brief itself: - The post page's highlight bar (#6556), scoped to a body container so a selection in the header or the preferences card never raises it. - A copy icon per bullet and paragraph, carrying the text and the brief link, after #6350's copy-summary icon. Each one is labelled with what it copies, since a brief carries a column of them. - A Snapshot on the Must know heading that captures its bullets as one card, with the claim before the colon as each line. None of these can be declared in JSX: the body is a single `<Markdown content={contentHtml} />`, so briefBodyBlocks reads the blocks back out of the rendered DOM — which is also the most faithful source, being what the reader is looking at — and the controls are portalled into them. Hosts are marked and swept before each run, because Markdown re-renders on its own for hover cards and the image modal. The closing band is #6369's ShareBand with the briefing's copy, so it cannot drift from the end-of-conversation band it was asked to match. All of it sits behind `briefing_share_controls`, default false. Mockup-to-eng-pass: 1 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich
added a commit
that referenced
this pull request
Sep 6, 2026
Three controls inside the brief itself: - The post page's highlight bar (#6556), scoped to a body container so a selection in the header or the preferences card never raises it. - A copy icon per bullet and paragraph, carrying the text and the brief link, after #6350's copy-summary icon. Each one is labelled with what it copies, since a brief carries a column of them. - A Snapshot on the Must know heading that captures its bullets as one card, with the claim before the colon as each line. None of these can be declared in JSX: the body is a single `<Markdown content={contentHtml} />`, so briefBodyBlocks reads the blocks back out of the rendered DOM — which is also the most faithful source, being what the reader is looking at — and the controls are portalled into them. Hosts are marked and swept before each run, because Markdown re-renders on its own for hover cards and the image modal. The closing band is #6369's ShareBand with the briefing's copy, so it cannot drift from the end-of-conversation band it was asked to match. All of it sits behind `briefing_share_controls`, default false. Mockup-to-eng-pass: 1 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 6, 2026
tomeredlich
added a commit
that referenced
this pull request
Sep 6, 2026
…placement The seven menu layouts were there to pick one, and the buttons they were picking between are not new — Share and Copy link already exist in the app. What this PR is actually about is generating the card and deciding where the control sits, so the exploration comes out: - Features/Snapshot/Menu styles, and the menuVariant prop behind it - SnapshotImageSection, which only the modal candidates rendered - SplitShareButton and CopyStateIcon, which belong to #6369 - the optional size and compact props on SocialShareList, SocialShareButton and SocialShareContainer, added only for those candidates SnapshotButton is a plain button again: press it and it shares the card. Rendering starts on hover or focus rather than on mount, so a feed does not carry a 1080px card per item — and because the render is already done by the time the press lands, the clipboard still has the gesture it needs. A press that arrives first waits for the render and downloads instead, which is the one path the clipboard cannot take. Two type fixes on SocialShareButton stay: the size map is indexed with the full ButtonSize union, and the ref is passed to a Button that renders as an anchor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #6349 (
claude/share-end-of-conversation), which is itself stacked on #6343. Review #6343 → #6349 → this one, or read this diff alone — it only touches the band and its share control.#6349 introduced the end-of-conversation share band as a bordered card with an icon-only share trigger, behind two feature flags. This PR is the design pass on top of it: the band goes flat by default, the trigger becomes a labelled split button, and the band ships to everyone.
Shipping to everyone
The band is no longer gated.
share_end_of_conversationis deleted (nothing else read it) and thesharing_visibilitykill-switch check is dropped from this component, so the comment threshold is the only condition. The flag anduseSharingVisibilitystay in the codebase — they gate the other surfaces in the initiative.That means no runtime off-switch for this band: turning it off after merge takes a revert. Flagged deliberately, per the product call to show it to everyone.
The band
flatis now the default variant: no fill, no card border, just a top hairline separating the strip from the comments above it.cardis still available viavariant="card"for surfaces that want the heavier self-contained block.The split copy control
ShareActionsgains asplitvariant — two real buttons that read as one control:prefers-reduced-motion).DropdownMenuwith the standard social tiles. It rotates to point back at the menu it opened.Geometry follows the button guidelines exactly rather than approximating them: at every
ButtonSizethe control matches a standard button's height, corner radius, type scale and outer padding. The only deviations are at the shared edge — both inner paddings tighten one step so the seam isn't sitting in a canyon, and the chevron drops the icon-only square so it hugs the seam symmetrically.The divider
The two halves are separated by a single rule, not two borders meeting:
border-r) — full height, matching the outer edge in width, colour and hover transition by constructiontailwind/buttons.tsgivesSubtle's borderTwo build-level gotchas worth knowing, both commented in the code:
bg-currentcompiles to nothing here — this theme replaces Tailwind'scolorswholesale and has nocurrentkey. A hairline drawn with it renders as no hairline, which is what let the two real borders read as a double rule in the first place.before:opacity-*does not survive this project's Tailwind build on pseudo-elements: the class lands in the DOM and the element renders at full strength. Both dividers mix their alpha into the colour instead.Structure
SplitShareButton.tsx— the control (padding maps, divider, chevron, dropdown).CopyStateIcon.tsx— the cross-fading copy/check glyph, shared with the mobile and popover paths.ShareActions.tsx— back to a variant router.The
iconvariant still uses the existing Popover rather thanDropdownMenu; migrating it would change the DOM of every icon-only share surface in the app, so it is deliberately left for a follow-up.Storybook
Components/Share/EndOfConversationShare— every state (below/at/above threshold, no comments, flat vs card, dark/light), plus a real 390px iframe for the mobile path.Components/Share/SplitShareButton— the split control across all five variants and all five sizes, and anAgainstStandardButtonspage that prints live computed geometry (height, radius, type, padding) beside the standard buttons so the guideline match is checkable rather than asserted.Verification
ShareActions.spec.tsx,EndOfConversationShare.spec.tsx,PostComments.spec.tsx, including a new test that opens the dropdown and asserts the social tiles are present. Tests for the now-removed flag paths are deleted rather than inverted.packages/webappPostPagesuite passes (48 tests) — cross-package check per AGENTS.md.Subtle's computedborderLeftColor.Seeing it on the preview
Open any post with more than 3 comments — no flags to toggle.
🤖 Generated with Claude Code
Preview domain
https://claude-share-split-copy-button.preview.app.daily.dev